#include "DebugRenderFeature.h"
// #include <SECore/ResManager.h>
#include <SERender/Renderer/Renderer.h>
#include <SERender/Resourses/Loader/VertexArrayLoader.h>
#include <SERender/Resourses/Mesh.h>
#include <SEngine/Render/Entity/Loader/ModelLoader.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <SEngine/Ecs/Components/LightComp.h>
#include <SEngine/Ecs/Components/CameraComp.h>
#include <SEngine/Render/Entity/BoundingBox.h>
#include <SECore/Manager.h>
#include <SERender/Resourses/Loader/ShaderLoader.h>


namespace SEngine
{
    DebugRenderFeature::DebugRenderFeature(Renderer &renderer)
        : RenderFeature(renderer) 
    {
        InitLightTextures();
        InitCircleDrawable();
        InitLineDrawable();
        InitGridDrawable();
        InitContourMaterial();
        InitColorIDMaterial();
        InitIconModel();
    }

    void DebugRenderFeature::BeginFrame()
    {

    }

    void DebugRenderFeature::DrawCircle(glm::mat4 model, glm::vec4 color, float lineWidth)
    {
        if (m_circleDrawable.IsValid()) {
            auto & pso = m_circleDrawable.GetMaterial().Value().GetPSO();
            pso.depth = true;
            m_circleDrawable.GetMaterial().Value().Set("u_model", model);
            m_circleDrawable.GetMaterial().Value().Set("u_diffuse", color);
            m_renderer.GetDrive()->SetLineWidth(lineWidth);
            m_renderer.DrawEntity(m_circleDrawable);
        }
    }

    void DebugRenderFeature::DrawLine(glm::vec3 begin, glm::vec3 end, glm::vec4 color, float lineWidth)
    {
        if (m_lineDrawable.IsValid()) {
            m_lineDrawable.GetMaterial().Value().Set("u_begin", begin);
            m_lineDrawable.GetMaterial().Value().Set("u_end", end);
            m_lineDrawable.GetMaterial().Value().Set("u_color", color);
            m_renderer.GetDrive()->SetLineWidth(lineWidth);
            m_renderer.DrawEntity(m_lineDrawable);
        }
    }

    void DebugRenderFeature::DrawGrid(float size, float gap, glm::vec4 color, float lineWidth, glm::mat4 model)
    {
        if (m_gridDrawable.IsValid()) {
            m_gridDrawable.GetMaterial().Value().Set("u_color", color);
            m_gridDrawable.GetMaterial().Value().Set("u_model", model);
            m_gridDrawable.GetMaterial().Value().Set("u_size", size);
            m_gridDrawable.GetMaterial().Value().Set("u_gap", gap);
            m_gridDrawable.SetInstanceCount((size_t)(size / gap));
            m_renderer.GetDrive()->SetLineWidth(lineWidth);
            m_gridDrawable.GetMaterial().Value().Set("u_type", 0);
            m_renderer.DrawEntity(m_gridDrawable);
            m_gridDrawable.GetMaterial().Value().Set("u_type", 1);
            m_renderer.DrawEntity(m_gridDrawable);
        }
    }

    void DebugRenderFeature::DrawBox(glm::vec3 pos, glm::vec3 halfSize, glm::vec4 color, float lineWidth, glm::mat4 model) 
    {
        auto Transform = [&] (glm::vec3 v) {
            return glm::vec3(model * glm::vec4{v, 1.0f});
        };
        DrawLine(Transform(pos + halfSize * glm::vec3{1.0f, 1.0f, 1.0f}), Transform(pos + halfSize * glm::vec3{1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{1.0f, -1.0f, 1.0f}), Transform(pos + halfSize * glm::vec3{-1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{-1.0f, -1.0f, 1.0f}), Transform(pos + halfSize * glm::vec3{-1.0f, 1.0f, 1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{-1.0f, 1.0f, 1.0f}), Transform(pos + halfSize * glm::vec3{1.0f, 1.0f, 1.0f}), color, lineWidth);

        DrawLine(Transform(pos + halfSize * glm::vec3{1.0f, 1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{1.0f, -1.0f, -1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{1.0f, -1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{-1.0f, -1.0f, -1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{-1.0f, -1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{-1.0f, 1.0f, -1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{-1.0f, 1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{1.0f, 1.0f, -1.0f}), color, lineWidth);

        DrawLine(Transform(pos + halfSize * glm::vec3{1.0f, 1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{1.0f, 1.0f, 1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{1.0f, -1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{-1.0f, -1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{-1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(Transform(pos + halfSize * glm::vec3{-1.0f, 1.0f, -1.0f}), Transform(pos + halfSize * glm::vec3{-1.0f, 1.0f, 1.0f}), color, lineWidth);
    }


    void DebugRenderFeature::DrawIcon(size_t lightHash, glm::mat4 modelMatrix, float scale)
    {
        DrawIconImpl(m_iconTexs[lightHash], modelMatrix, scale);
    }

    void DebugRenderFeature::DrawIconID(glm::mat4 modelMatrix, uint8_t id, float scale)
    {
        m_modelIcon.GetMaterial().Value().Set("u_id", (float)id / 255.0f);
        m_modelIcon.GetMaterial().Value().Set("u_model", modelMatrix);
        m_modelIcon.GetMaterial().Value().Set("u_scale", scale);
        m_renderer.DrawEntity(m_modelIcon);
    }

    // void DebugRenderFeature::DrawBoundingGraphics(BoundingGraphics &bounding, glm::vec4 color, float lineWidth)
    // {
    //     if (typeid(BoundingBox).hash_code() == bounding.GetID()) {
    //         auto & box = bounding.Cast<BoundingBox>();
    //         auto & vertices = box.GetBox();
    //         DrawLine(vertices[0], vertices[1], color, lineWidth);
    //         DrawLine(vertices[1], vertices[2], color, lineWidth);
    //         DrawLine(vertices[2], vertices[3], color, lineWidth);
    //         DrawLine(vertices[3], vertices[0], color, lineWidth);
    //         DrawLine(vertices[4], vertices[5], color, lineWidth);
    //         DrawLine(vertices[5], vertices[6], color, lineWidth);
    //         DrawLine(vertices[6], vertices[7], color, lineWidth);
    //         DrawLine(vertices[7], vertices[4], color, lineWidth);
    //         DrawLine(vertices[0], vertices[4], color, lineWidth);
    //         DrawLine(vertices[1], vertices[5], color, lineWidth);
    //         DrawLine(vertices[2], vertices[6], color, lineWidth);
    //         DrawLine(vertices[3], vertices[7], color, lineWidth);
    //     }
    // }

    void DebugRenderFeature::DrawCameraLine(Camera &camera, float width, float height, glm::vec4 color, float lineWidth)
    {
        glm::mat4 invp = glm::inverse(camera.GetProjectMatrix(width, height) * camera.GetViewMatrix());
        auto GetWorldPos = [invp] (glm::vec3 vertex) {
            auto v = invp * glm::vec4(vertex, 1.0f);
            return glm::vec3{v / v.w};
        };
        DrawLine(GetWorldPos(glm::vec3{-1.0f, -1.0f, -1.0f}), GetWorldPos(glm::vec3{-1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{1.0f, -1.0f, -1.0f}), GetWorldPos(glm::vec3{1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{1.0f, 1.0f, -1.0f}), GetWorldPos(glm::vec3{1.0f, 1.0f, 1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{-1.0f, 1.0f, -1.0f}), GetWorldPos(glm::vec3{-1.0f, 1.0f, 1.0f}), color, lineWidth);

        DrawLine(GetWorldPos(glm::vec3{-1.0f, -1.0f, -1.0f}), GetWorldPos(glm::vec3{1.0f, -1.0f, -1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{1.0f, 1.0f, -1.0f}), GetWorldPos(glm::vec3{1.0f, -1.0f, -1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{1.0f, 1.0f, -1.0f}), GetWorldPos(glm::vec3{-1.0f, 1.0f, -1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{-1.0f, -1.0f, -1.0f}), GetWorldPos(glm::vec3{-1.0f, 1.0f, -1.0f}), color, lineWidth);

        DrawLine(GetWorldPos(glm::vec3{-1.0f, -1.0f, 1.0f}), GetWorldPos(glm::vec3{1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{1.0f, 1.0f, 1.0f}), GetWorldPos(glm::vec3{1.0f, -1.0f, 1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{1.0f, 1.0f, 1.0f}), GetWorldPos(glm::vec3{-1.0f, 1.0f, 1.0f}), color, lineWidth);
        DrawLine(GetWorldPos(glm::vec3{-1.0f, -1.0f, 1.0f}), GetWorldPos(glm::vec3{-1.0f, 1.0f, 1.0f}), color, lineWidth);
    }

    void DebugRenderFeature::DrawBoundingBox(BoundingBox &box, glm::mat4 model, glm::vec4 color, float lineWidth)
    {
        for (auto [v1, v2] : box.Lines()) {
            DrawLine(glm::vec3(model * glm::vec4(v1, 1.0f)), glm::vec3(model * glm::vec4(v2, 1.0f)), color, lineWidth);
        }
    }

    void DebugRenderFeature::InitCircleDrawable()
    {
        VertexArrayLoader<3> loader(std::make_unique<CurrentDrive>());
        const int count = 36;
        const float gap = 360.0f / count;
        glm::vec3 last{0.0f};
        for (float anglePos = 0.0f; anglePos <= 360.0f; anglePos += gap) {
            glm::vec3 cur{glm::cos(glm::radians(anglePos)), glm::sin(glm::radians(anglePos)), 0.0f};
            if (anglePos != 0) {
                loader.PushVertex({last.x, last.y, last.z});
                loader.PushVertex({cur.x, cur.y, cur.z});   
            }
            last = cur;
        }
        m_circleDrawable.SetMesh(std::make_unique<Mesh>(loader.CreateVertexArray()));
        auto material = Manager::GetInstance().Get<Material>("Unlit").value();
        m_circleDrawable.SetMaterial(material);
        m_circleDrawable.SetMode(ElemMode::LINES);
        m_circleDrawable.SetAPIType(DrawAPIType::DrawArrays);
    }

    void DebugRenderFeature::DrawContour(Drawable &drawable)
    {
        CurrentDrive().SetLineWidth(4.0f);
        CurrentDrive().SetPolygonMode(GraphicsFace::FRONT_AND_BACK, GraphicsFaceMode::LINE);
        auto oldMaterial = drawable.GetMaterial();
        drawable.SetMaterial(m_contourMaterial);
        m_renderer.DrawEntity(drawable);
        // m_contourMaterial = drawable.GetMaterial();
        drawable.SetMaterial(oldMaterial);
        CurrentDrive().SetPolygonMode(GraphicsFace::FRONT_AND_BACK, GraphicsFaceMode::FILL);
    }

    void DebugRenderFeature::DrawContour(Model &model)
    {
        CurrentDrive().SetLineWidth(4.0f);
        CurrentDrive().SetPolygonMode(GraphicsFace::FRONT_AND_BACK, GraphicsFaceMode::LINE);
        m_contourMaterial.Value().Set("Diffuse", glm::vec4{1.0f, 1.0f, 0.0f, 1.0f});
        model.Draw(m_renderer, m_contourMaterial);
        CurrentDrive().SetPolygonMode(GraphicsFace::FRONT_AND_BACK, GraphicsFaceMode::FILL);
    }

    void DebugRenderFeature::DrawModelWithID(Model &model, uint32_t id)
    {
        uint8_t * pid = (uint8_t*)&id;
        glm::vec4 u_id{
            (float)pid[0] / 255.0f,
            (float)pid[1] / 255.0f,
            (float)pid[2] / 255.0f,
            (float)pid[3] / 255.0f
        };
        m_colorIDMaterial.Value().Set("u_id", u_id);
        m_colorIDMaterial.Value().Set("u_model", model.Transfrom());
        model.Draw(m_renderer, m_colorIDMaterial);
    }

    void DebugRenderFeature::InitLineDrawable()
    {
        VertexArrayLoader<3> loader(std::make_unique<CurrentDrive>());
        loader.PushVertex({0.0f, 0.0f, 0.0f});
        loader.PushVertex({0.0f, 0.0f, 0.0f});
        m_lineDrawable.SetMesh(std::make_unique<Mesh>(loader.CreateVertexArray()));
        auto material = Manager::GetInstance().Get<Material>("Line").value();
        m_lineDrawable.SetMaterial(material);
        m_lineDrawable.SetMode(ElemMode::LINES);
        m_lineDrawable.SetAPIType(DrawAPIType::DrawArrays);
    }

    void DebugRenderFeature::InitGridDrawable()
    {
        VertexArrayLoader<3> loader(std::make_unique<CurrentDrive>());
        loader.PushVertex({0.0f, 0.0f, 0.0f});
        loader.PushVertex({0.0f, 0.0f, 0.0f});
        m_gridDrawable.SetMesh(std::make_unique<Mesh>(loader.CreateVertexArray()));
        auto material = Manager::GetInstance().Get<Material>("Grid").value();
        material.Value().GetPSO().blend = true;
        m_gridDrawable.SetMaterial(material);
        m_gridDrawable.SetMode(ElemMode::LINES);
        m_gridDrawable.SetAPIType(DrawAPIType::DrawArrays);

    }

    void DebugRenderFeature::InitContourMaterial() 
    {
        m_contourMaterial = Manager::GetInstance().Get<Material>("Unlit").value();
        auto & pso = m_contourMaterial.Value().GetPSO();
        pso.cullFace = false;
        pso.cullFront = false;
        pso.depth = false;
    }

    void DebugRenderFeature::InitColorIDMaterial()
    {
        m_colorIDMaterial = Manager::GetInstance().Get<Material>("ColorID").value();
    }

    void DebugRenderFeature::InitIconModel()
    {
        VertexArrayLoader<3, 2> loader(std::make_unique<CurrentDrive>());
        loader.PushVertex({-1.0f, -1.0f, 0.0f, 0.0f, 0.0f});
        loader.PushVertex({1.0f, -1.0f, 0.0f, 1.0f, 0.0f});
        loader.PushVertex({1.0f, 1.0f, 0.0f, 1.0f, 1.0f});
        loader.PushVertex({-1.0f, 1.0f, 0.0f, 0.0f, 1.0f});
        loader.PushFace({0, 1, 2});
        loader.PushFace({0, 2, 3});
        auto material = Manager::GetInstance().Get<Material>("Image").value();
        m_modelIcon.SetMaterial(material);
        m_modelIcon.SetMesh(std::make_unique<Mesh>(loader.CreateVertexArray()));
        m_modelIcon.SetAPIType(DrawAPIType::DrawElements);
        m_modelIcon.SetMode(ElemMode::TRIANGLES);
        auto & pso = m_modelIcon.GetMaterial().Value().GetPSO();
        pso.blend = true;
        pso.depth = false;
    }

    void DebugRenderFeature::InitLightTextures()
    {
        m_iconTexs.emplace(typeid(PointLightComp).hash_code(), Manager::GetInstance().Get<Texture>("PointLight").value());
        m_iconTexs.emplace(typeid(DirectLightComp).hash_code(), Manager::GetInstance().Get<Texture>("DirectLight").value());
        m_iconTexs.emplace(typeid(AmbientLightSphereComp).hash_code(), Manager::GetInstance().Get<Texture>("AmbientSphereLight").value());
        m_iconTexs.emplace(typeid(CameraComp).hash_code(), Manager::GetInstance().Get<Texture>("Camera").value());
    }

    void DebugRenderFeature::DrawIconImpl(ResRef<Texture> tex, glm::mat4 modelMatrix, float scale)
    {
        m_modelIcon.GetMaterial().Value().Set("u_texture", Shader::Sampler{tex.Value(), TextureSlot::TEXTURE0});
        m_modelIcon.GetMaterial().Value().Set("u_id", (float)0.0f);
        m_modelIcon.GetMaterial().Value().Set("u_model", modelMatrix);
        m_modelIcon.GetMaterial().Value().Set("u_scale", scale);
        m_renderer.DrawEntity(m_modelIcon);
    }
} // namespace SEngine
